// Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{collections::BTreeMap, str::FromStr};

use serde::{de::Visitor, ser::SerializeStruct, Deserialize, Serialize};

use crate::{Arch, Pkg, PkgSpec, Version};

#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SpecsConfig(pub BTreeMap<String, PkgSpecConfig>);

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PkgSpecConfig {
    pub name: Option<String>,
    pub version: Version,
    pub features: Vec<String>,
    pub arch: Arch,
}

impl Serialize for PkgSpecConfig {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer
    {
        let serialize_name = self.name.is_some();
        let serialize_features = !self.features.is_empty();
        let serialize_arch = !self.arch.is_host();
        let len = serialize_name as usize + serialize_features as usize + serialize_arch as usize + 1;

        if len == 1 {
            Version::serialize(&self.version, serializer)
        } else {
            let mut s = serializer.serialize_struct("PkgSpecConfig", len)?;
            if serialize_name {
                s.serialize_field("name", self.name.as_ref().unwrap())?;
            }
            s.serialize_field("version", &self.version)?;
            if serialize_features {
                s.serialize_field("features", &self.features)?;
            }
            if serialize_arch {
                s.serialize_field("arch", &self.arch)?;
            }
            s.end()
        }
    }
}

impl<'de> Deserialize<'de> for PkgSpecConfig {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>
    {
        #[derive(Deserialize)]
        #[serde(field_identifier, rename_all = "kebab-case")]
        enum Field {
            Name,
            Version,
            Features,
            Arch,
        }

        struct PkgSpecConfigVisitor;

        impl<'de> Visitor<'de> for PkgSpecConfigVisitor {
            type Value = PkgSpecConfig;
        
            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                formatter.write_str("PkgSpecConfig")
            }

            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                let version = Version::from_str(v).map_err(serde::de::Error::custom)?;
                Ok(PkgSpecConfig {
                    name: None,
                    version,
                    features: Vec::new(),
                    arch: Arch::host(),
                })
            }

            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
            where
                A: serde::de::MapAccess<'de>,
            {
                let mut name = None;
                let mut version = None;
                let mut features = None;
                let mut arch = None;
                while let Some(k) = map.next_key()? {
                    match k {
                        Field::Name => {
                            if name.is_some() {
                                return Err(serde::de::Error::duplicate_field("name"));
                            }
                            name = Some(map.next_value()?);
                        },
                        Field::Version => {
                            if version.is_some() {
                                return Err(serde::de::Error::duplicate_field("version"));
                            }
                            version = Some(map.next_value()?);
                        },
                        Field::Features => {
                            if features.is_some() {
                                return Err(serde::de::Error::duplicate_field("features"));
                            }
                            features = Some(map.next_value()?);
                        },
                        Field::Arch => {
                            if arch.is_some() {
                                return Err(serde::de::Error::duplicate_field("arch"));
                            }
                            arch = Some(map.next_value()?);
                        },
                    }
                }

                let version = version.ok_or(serde::de::Error::missing_field("version"))?;
                let features = features.unwrap_or_default();
                let arch = arch.unwrap_or_default();
                Ok(PkgSpecConfig {
                    name,
                    version,
                    features,
                    arch,
                })
            }
        }
        
        deserializer.deserialize_any(PkgSpecConfigVisitor)
    }
}

impl SpecsConfig {
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    pub fn into_pkg_spec_iter(self) -> impl Iterator<Item = PkgSpec> {
        self.0.into_iter().map(|(k, v)| v.into_pkg_spec(k))
    }

    pub fn to_pkg_spec_iter(&self) -> impl Iterator<Item = PkgSpec> + use<'_> {
        self.0.iter().map(|(k, v)| v.to_pkg_spec(k))
    }
}

impl PkgSpecConfig {
    pub fn into_pkg_spec(self, name: String) -> PkgSpec {
        PkgSpec {
            pkg: Pkg {
                name: self.name.unwrap_or(name),
                version: self.version,
            },
            features: self.features,
            arch: self.arch,
        }
    }

    pub fn to_pkg_spec(&self, name: &String) -> PkgSpec {
        PkgSpec {
            pkg: Pkg {
                name: self.name.as_ref().unwrap_or(name).to_string(),
                version: self.version.clone(),
            },
            features: self.features.clone(),
            arch: self.arch,
        }
    }
}

#[cfg(test)]
mod test {
    use std::collections::BTreeMap;

    use crate::{config::{specs::PkgSpecConfig, SpecsConfig}, Arch, Version};

    #[test]
    fn test_specs_config_serialize() {
        assert_eq!(
            toml::to_string_pretty(&SpecsConfig([
                (
                    "pkg1".to_string(),
                    PkgSpecConfig {
                        name: None,
                        version: Version::from_major(1),
                        features: vec![],
                        arch: Arch::host(),
                    },
                ),
                (
                    "pkg2".to_string(),
                    PkgSpecConfig {
                        name: Some("pkg3".to_string()),
                        version: Version::from_major_minor(1, 2),
                        features: vec!["feature1".to_string()],
                        arch: Arch::host(),
                    },
                ),
            ].into_iter().collect::<BTreeMap<String, PkgSpecConfig>>())),
            Ok("pkg1 = \"1\"\n\
                \n\
                [pkg2]\n\
                name = \"pkg3\"\n\
                version = \"1.2\"\n\
                features = [\"feature1\"]\n".to_string())
        );
    }

    #[test]
    fn test_specs_config_deserialize() {
        assert_eq!(
            toml::from_str("pkg1 = \"1\"\n\
                            pkg2 = { name = \"pkg3\", version = \"1.2\", features = [\"feature1\"] }"),
            Ok(SpecsConfig([
                (
                    "pkg1".to_string(),
                    PkgSpecConfig {
                        name: None,
                        version: Version::from_major(1),
                        features: vec![],
                        arch: Arch::host(),
                    },
                ),
                (
                    "pkg2".to_string(),
                    PkgSpecConfig {
                        name: Some("pkg3".to_string()),
                        version: Version::from_major_minor(1, 2),
                        features: vec!["feature1".to_string()],
                        arch: Arch::host(),
                    },
                ),
            ].into_iter().collect::<BTreeMap<String, PkgSpecConfig>>()))
        );
    }
}